import re
import os
import logging
import mimetypes
import urllib.parse
from io import IOBase
from minio import Minio
from minio.helpers import MIN_PART_SIZE

from dtcloud.http import request, Response

_logger = logging.getLogger(__name__)


class BaseMinioError(Exception):
    code = "BaseMinioError"
    message = ""

    def __init__(self, func, *args, **kwargs):
        self.func = func.func.__name__  # 偏函数.func.__main__ 是实际调用的对象名称
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return f"{self.code}:{self.message},func:{self.func},args:{self.args},kwargs:{self.kwargs}"


class ObjectTypeError(BaseMinioError):
    code = "ObjectTypeError"
    message = "The data must be 'str'/'bytes'/'IO object' "

    def __init__(self):
        pass

    def __str__(self):
        return f"{self.code}:{self.message}"


class NoSuchKey(BaseMinioError):
    code = "NoSuchKey"
    message = "Object does not exist"


class DataDecorator(object):
    def __init__(self, data):
        self.offset = 0
        try:
            self.data = data.encode("utf8")
        except AttributeError:
            self.data = data

    def read(self, size: int = None):
        if size is None:
            return self.data
        result = self.data[self.offset: self.offset + size]
        self.offset += size
        return result


_EXTRA_TYPES_MAP = {
    ".js": "application/javascript",
    ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    ".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
    ".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
    ".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
    ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    ".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
    ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    ".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
    ".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
    ".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
    ".apk": "application/vnd.android.package-archive",
}


def content_type_by_name(name):
    """根据文件名，返回Content-Type。"""
    ext = os.path.splitext(name)[1].lower()
    if ext in _EXTRA_TYPES_MAP:
        return _EXTRA_TYPES_MAP[ext]

    return mimetypes.guess_type(name)[0]


class MinioOss:
    def __init__(self, host, access_key, secret_key, bucket, model_dirs):
        self.host = host
        self.access_key = access_key
        self.secret_key = secret_key
        self.secure = False
        self.bucket = bucket
        self.model_dirs = model_dirs
        self.client = Minio(
            self.host,
            secure=self.secure,
            access_key=self.access_key,
            secret_key=self.secret_key,
        )

    def __new__(cls, *args, **kw):
        """
        单例
        """
        if not hasattr(cls, 'instance'):
            cls.instance = super(MinioOss, cls).__new__(cls)
        return cls.instance

    def parse_path(self, path):
        """
        @param path:文件路径
        @return:
        """
        if path.startswith("/"):
            return f"{self.model_dirs}{path}"
        else:
            return f"{self.model_dirs}/{path}"

    def object_exists(self, bucket_name, path: str) -> bool:
        """
        判断文件是否存在
        @param path: 文件路径
        @return:
        """
        path = self.parse_path(path)

        try:
            self.client.stat_object(bucket_name, path)
        except NoSuchKey:
            return False

        return True

    def upload(self, object_name, raw_data, length=None):
        """
        上传IO流到Minio
        @param object_name: 对象在桶中的名字
        @param raw_data: An object having callable read() returning bytes object
        @param expire: 过期时间 单位：s
        @param length: 文件大小
        @return:
        """
        object_name = self.parse_path(object_name)
        # 当上传数据不是 str bytes 和IO对象时报错
        if not isinstance(raw_data, (IOBase, str, bytes)):
            raise ObjectTypeError()

        if not isinstance(raw_data, IOBase):
            raw_data = DataDecorator(raw_data)

        found = self.client.bucket_exists(self.bucket)
        if not found:
            self.client.make_bucket(self.bucket)
            _logger.info(f'create {self.bucket} sucess！')

        if length > 0:
            # 如果知道上传文件的大小
            return self.client.put_object(
                bucket_name=self.bucket, object_name=object_name, data=raw_data, length=length,
                content_type=content_type_by_name(object_name)
            )
        else:
            # 如果不知道文件的大小
            # 将大数据分片上传 每一片的数据为5M
            # length=-1 表示不知道文件的大小
            return self.client.put_object(
                bucket_name=self.bucket, object_name=object_name, data=raw_data, length=-1,
                content_type=content_type_by_name(object_name), part_size=MIN_PART_SIZE
            )

    def upload_file(self, object_name, file_name):
        """
        上传文件到Minio
        @param object_name: 桶里面的对象名
        @param file_name: 上传的文件名
        @return:
        """
        object_name = self.parse_path(object_name)

        found = self.client.bucket_exists(self.bucket)
        if not found:
            self.client.make_bucket(self.bucket)
            _logger.info(f'create {self.bucket} sucess！')

        self.client.fput_object(
            self.bucket, object_name, file_name, content_type=content_type_by_name(object_name))

    def download_url(self, object_name):
        """
        获取minio文件下载地址
        @param object_name: 桶里面的对象名
        @return:
        """
        if re.findall('[\u4e00-\u9fa5]', object_name):
            # 当文件名为中文时，minio会自己转换，无需再通过urllib.parse.quote转换
            return self.client.presigned_get_object(self.bucket, self.parse_path(object_name))
        return self.client.presigned_get_object(self.bucket, urllib.parse.quote(self.parse_path(object_name)))

    def delete(self, object_name):
        path = self.parse_path(object_name)
        self.client.remove_object(self.bucket, path)


if __name__ == '__main__':
    # miniostore_ = MinioOss("127.0.0.1:9900", "test", "test123456", "rasamodels", "/home/huangqh/rasa3/models/")
    # print(MinioOss.put_object("helloworld", io.BytesIO(b"Hello world !"), 12))
    # print(MinioOss.get_object("helloworld"))
    """
    self.host = host
        self.access_key = access_key
        self.secret_key = secret_key
        self.secure = False
        self.bucket = bucket
        self.model_dirs = model_dirs
    """

    # config = {
    #     "access_key": "miniadmin",
    #     "secret_key": "admin123456",
    #     "host": "121.41.206.83:9000",
    #     "bucket": "szhss",
    #     "model_dirs": "hss"
    # }

    config = {
        "access_key": "Kc4kn7wsAjvIp46MyARw",
        "secret_key": "OhZHBSAQX8TXGdeM7VdOPSfex3Y5sQVi1dtJR4vn",
        "host": "10.201.97.35:9000",
        "bucket": "his",
        "model_dirs": "qy"
    }

    oss = MinioOss(**config)
    # object_name = 'b.txt'
    # file_path = 'a.txt'
    #
    # from io import BytesIO
    # with open(file_path, "rb") as file_data:
    #     f = BytesIO(file_data.read())
    #     # print(f.read())
    #     length =os.stat(file_path).st_size
    #
    #     oss.upload(object_name, f, length)
    #
    object_name = '巡更点导入模板.xlsx'
    print(oss.download_url(object_name))
